/*
 *      Copyright 2012-2013 Battams, Derek
 *       
 *       Licensed under the Apache License, Version 2.0 (the "License");
 *       you may not use this file except in compliance with the License.
 *       You may obtain a copy of the License at
 *
 *          http://www.apache.org/licenses/LICENSE-2.0
 *
 *       Unless required by applicable law or agreed to in writing, software
 *       distributed under the License is distributed on an "AS IS" BASIS,
 *       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *       See the License for the specific language governing permissions and
 *       limitations under the License.
 */
package org.schedulesdirect.grabber;

import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.schedulesdirect.api.Config;
import org.schedulesdirect.api.EpgClient;
import org.schedulesdirect.api.Headend;
import org.schedulesdirect.api.Lineup;
import org.schedulesdirect.api.Program;
import org.schedulesdirect.api.Station;
import org.schedulesdirect.api.ZipEpgClient;

import com.beust.jcommander.ParameterException;

/**
 * A debugging tool used to parse and inspect the raw data feeds and dump reports; mainly used for API development and debugging
 * 
 * @author Derek Battams &lt;derek@battams.ca&gt;
 *
 */
public final class Auditor {
	static private final Logger LOG = Logger.getRootLogger();
	
	private CommandAudit opts;
	private boolean failed = false;
	
	Auditor(CommandAudit opts) {
		this.opts = opts;
	}
	
	public void run() {
		try {
			if(!opts.isAuditJson() && !opts.isAuditScheds())
				throw new ParameterException("Must specify at least one of --scheds or --json");
			if(opts.isAuditJson())
				auditJson();
			if(opts.isAuditScheds())
				auditScheds();
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
		
	private void auditScheds() throws IOException, JSONException, ParseException {
		Map<Integer, JSONObject> stations = getStationMap();
		ZipFile zip = new ZipFile(opts.getSrc());
		final SimpleDateFormat FMT = Config.get().getDateTimeFormat(); 
		@SuppressWarnings("unchecked")
		Enumeration<ZipEntry> scheds = (Enumeration<ZipEntry>) zip.entries();
		while(scheds.hasMoreElements()) {
			ZipEntry sched = scheds.nextElement();
			if(sched.getName().startsWith("schedules/")) {
				boolean failed = false;
				int id = getStationIdFromFileName(sched.getName());
				JSONObject station = stations.get(id);
				StringBuilder msg = new StringBuilder(String.format("Inspecting %s (%d)... ", station.getString("callsign"), id));
				InputStream ins = zip.getInputStream(sched);
				String input = IOUtils.toString(ins, "UTF-8");
				ins.close();
				JSONArray jarr = new JSONArray(input);
				for(int i = 1; i < jarr.length(); ++i) {
					long start = FMT.parse(jarr.getJSONObject(i).getString("airDateTime")).getTime();
					JSONObject prev = jarr.getJSONObject(i - 1);
					long prevStart = FMT.parse(prev.getString("airDateTime")).getTime() + 1000L * prev.getLong("duration");
					if(prevStart != start) {
						msg.append(String.format("FAILED! [%s]", prev.getString("airDateTime")));
						LOG.error(msg);
						failed = true;
						this.failed = true;
						break;
					}
				}
				if(!failed) {
					msg.append("PASSED!");
					LOG.info(msg);
				}
			}
		}
		zip.close();
	}
	
	private int getStationIdFromFileName(String f) {
		return Integer.parseInt(f.substring(f.indexOf('/') + 1, f.lastIndexOf('.')));
	}
	
	private Map<Integer, JSONObject> getStationMap() throws IOException, JSONException {
		Map<Integer, JSONObject> map = new HashMap<Integer, JSONObject>();
		ZipFile zip = new ZipFile(opts.getSrc());
		@SuppressWarnings("unchecked")
		Enumeration<ZipEntry> lineups = (Enumeration<ZipEntry>) zip.entries();
		while(lineups.hasMoreElements()) {
			ZipEntry sched = lineups.nextElement();
			if(sched.getName().startsWith("lineups/")) {
				InputStream ins = zip.getInputStream(sched);
				String input = IOUtils.toString(ins, "UTF-8");
				ins.close();
				JSONArray jarr = new JSONArray(new JSONObject(input).getJSONArray("stations").toString());
				for(int i = 0; i < jarr.length(); ++i) {
					JSONObject jobj = jarr.getJSONObject(i);
					int id = jobj.getInt("stationID");
					if(!map.containsKey(id))
						map.put(id, jobj);
				}
			}
		}
		zip.close();
		return map;
	}
	
	private void auditJson() throws Exception {
		EpgClient clnt = new ZipEpgClient(opts.getSrc());
		int i = 0;
		long start = System.currentTimeMillis();
		for(Headend h : clnt.getHeadends())
			for(Lineup l : h.getLineups())
				for(Station s : l.getStations())
					for(Program p : s.getPrograms()) {
						if(++i % 1000 == 0 && LOG.isInfoEnabled())
							LOG.info(String.format("Scanned %d objects!", i));
						try {
							String id = p.getId();
							if(LOG.isDebugEnabled())
								LOG.debug(String.format("Loaded object '%s'", id));
						} catch(Exception e) {
							LOG.error("Exception caught!", e);
							failed = true;
						}
					}
		LOG.warn(String.format("JSON validity audit of %d objects completed in %.1f seconds!", i, (1.0F * System.currentTimeMillis() - start) / 1000));
	}

	/**
	 * @return the failed
	 */
	public boolean isFailed() {
		return failed;
	}
}
